﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BeanboxSoftware.BeanMap.Internal
{
  public abstract partial class DictionaryCommonBase<TKey, TValue> : IDictionary<TKey, TValue>
  {
    protected Func<bool, IDictionary<TKey, TValue>> _passThrough;
    protected Action _removalCleanup;

    private IDictionary<TKey, TValue> _data;

    protected IDictionary<TKey, TValue> RetrieveDataHandle()
    {
      if (_passThrough == null)
      {
        return _data;
      }

      var pt = _passThrough(false);
      if (pt == null)
      {
        // passthrough means this represents a parent map, a null return means this represents no current data
        // if no current data, return the _data object because it should be empty
        // the only write method (indexer set) should call _passThrough(true) to build _this_ data
        return _data;
      }

      return pt;
    }

    protected void RemovalCleanup()
    {
      if (_passThrough != null)
      {
        _removalCleanup();
      }
    }

    protected IDictionary<TKey, TValue> SafeGet()
    {
      return _passThrough == null ? _data : _passThrough(true);
    }

    public DictionaryCommonBase()
    {
      _data = new Dictionary<TKey, TValue>();
    }

    protected void SetPassthrough(Func<bool, IDictionary<TKey, TValue>> passThrough, Action removalCleanup)
    {
      _passThrough = passThrough;
      _removalCleanup = removalCleanup;
    }

    void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
    {
      throw new NotSupportedException(MapDeclarations.MapInjectErrorMessage);
    }

    ICollection<TKey> IDictionary<TKey, TValue>.Keys
    {
      get { return RetrieveDataHandle().Keys; }
    }

    ICollection<TValue> IDictionary<TKey, TValue>.Values
    {
      get
      {
        var values = RetrieveDataHandle().Select(kvp => IndexerGet(kvp.Key));
        return values.ToEnumerableCollection();
      }
    }

    TValue IDictionary<TKey, TValue>.this[TKey key]
    {
      get
      {
        return IndexerGet(key);
      }
      set
      {
        IndexerSet(key, value);
      }
    }

    void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
    {
      throw new NotSupportedException(MapDeclarations.MapInjectErrorMessage);
    }

    bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
    {
      throw new NotSupportedException(MapDeclarations.MapInjectErrorMessage);
    }

    void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] buffer, int arrayIndex)
    {
      if (buffer == null)
      {
        throw new ArgumentNullException(MapDeclarations.CopyToBufferNullErrorMessage);
      }
      if (arrayIndex < 0 || arrayIndex > buffer.Length)
      {
        throw new ArgumentOutOfRangeException(MapDeclarations.CopyToIndexErrorMessage);
      }

      var keys = ((IDictionary<TKey, TValue>)this).Keys;

      if (arrayIndex + keys.Count > buffer.Length)
      {
        throw new ArgumentOutOfRangeException(MapDeclarations.CopyToIndexErrorMessage);
      }

      foreach (var key in keys)
      {
        buffer[arrayIndex++] = new KeyValuePair<TKey, TValue>(key, IndexerGet(key));
      }
    }

    bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
    {
      get { return false; }
    }

    bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
    {
      throw new NotSupportedException(MapDeclarations.MapInjectErrorMessage);
    }

    IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
    {
      return RetrieveDataHandle().Select(kvp => new KeyValuePair<TKey, TValue>(kvp.Key, IndexerGet(kvp.Key))).GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
      return ((IEnumerable<KeyValuePair<TKey, TValue>>)this).GetEnumerator();
    }

    public bool ContainsKey(TKey key)
    {
      return RetrieveDataHandle().ContainsKey(key);
    }

    public bool Remove(TKey key)
    {
      var result = RetrieveDataHandle().Remove(key);
      RemovalCleanup();
      return result;
    }

    public void Clear()
    {
      RetrieveDataHandle().Clear();
    }

    public abstract int Count { get; }
    public abstract bool TryGetValue(TKey key, out TValue value);
    protected abstract TValue IndexerGet(TKey key);
    protected virtual void IndexerSet(TKey key, TValue value)
    {
      throw new NotSupportedException(MapDeclarations.MapInjectErrorMessage);
    }
  }
}
